package main;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;

/**
 *
 * @author Mark van der Wal
 */
public class NetworkController 
{
    // attributes constant
    private final int SERVER_LISTENING_PORT = 1337;
    
    // attributes states
    private boolean mIsSimulationStarted;
    private String mIP;
    private NIOThread mNIOThread;
    private SensorSender mSensorSender;
    private ServerSocket mListeningSocket;
    private Socket mClientSocket;
    private DataInputStream mIStream;
    private DataOutputStream mOStream;
    private NetworkListener mNetListener;
    
    /**
     * Initialize important variables
     */
    public NetworkController ()
    {
        mIsSimulationStarted = false;
        mNetListener = null;
        mSensorSender = null;
    }
    
    /**
     * 
     * @return returns if the server has started or not
     */
    public synchronized boolean IsServerStarted() { return mIsSimulationStarted; }
    
    /**
     * Listener for network events
     * @param listener 
     */
    public void SetNetworkListener(NetworkListener listener) { mNetListener = listener; }
    
    public void Send(byte[] data)
    {
        if(mIsSimulationStarted)
        {
            try
            {
                mOStream.write(data);
            }
            catch(Exception ex)
            {
                Logger.AddLog("No connection has been established!");
            }
        }
    }
    
    /**
     * Start the server
     */
    public boolean Start()
    {
        try
        {           
            // create the network input output thread
            mListeningSocket = new ServerSocket(SERVER_LISTENING_PORT);
            mNIOThread = new NIOThread();
            mNIOThread.start();
            mIsSimulationStarted = true;

            // log the serverstart
            Logger.AddLog("Started listening! on " + InetAddress.getLocalHost().getHostAddress());
            
            return true;
        }
        catch(IOException e)
        {
            Logger.AddLog("Could not listen on the given port!");
        }
        catch(Exception e)
        {
            Logger.AddLog("NetworkController.Start() Error: " + e.getMessage());
        }
        
        return false;
    }
    
    /**
     * Stop the network controller
     */
    public void Stop()
    {
        // stop the listener thread
        mIsSimulationStarted = false;
        
        if(mSensorSender != null)
        {
            mSensorSender.terminate();

            try
            {
                mSensorSender.join();
            }
            catch(InterruptedException ex)
            {
                Logger.AddLog("NetworkController.Stop() Error: " + ex.getMessage());
            }
        }
        
        mNIOThread.terminate();
                
        try
        {
            mNIOThread.join();
        }
        catch(InterruptedException ex)
        {
            Logger.AddLog("NetworkController.Stop() Error: " + ex.getMessage());
        }

        mSensorSender = null;
        mNIOThread = null;
        
        // log the stop
        Logger.AddLog("The simulation stopped!");
    }
    
    /**
     * Inner class
     * @author Mark van der Wal
     */
    public class NIOThread extends Thread
    {
         /**
         * Start the server
         */
        @Override
        public void start()
        {
            super.start();
        }

            /**
         * run the listening thread
         */
        @Override
        public void run() 
        {          
            // the message we use
            NetEvent msg = null;
            
            // byteBuffer
            byte[] rawbuffer = new byte[128];
            
            //  run the listening thread
            try
            {
                // start handling the input
                while(mIsSimulationStarted)
                {
                    // listening for incoming connections
                    mClientSocket = mListeningSocket.accept();
                    mClientSocket.setTcpNoDelay(true);
                    
                    // save the ip address
                    mIP = mClientSocket.getLocalAddress().toString();
                    
                    // send message that we got a user connected
                    msg = new NetEvent();
                    msg.setEventID(NetEvent.NetEventType.TYPE_CON);
                    msg.setMsg(mIP + " connected!!");
                    publishProgress(msg);
                    
                    // get the streams
                    mIStream = new DataInputStream(new BufferedInputStream(mClientSocket.getInputStream()));
                    mOStream = new DataOutputStream(new BufferedOutputStream(mClientSocket.getOutputStream()));
                    
                    // start the sensor sender
                    mSensorSender = new SensorSender();
                    mSensorSender.start();
                    
                    // handle input
                    while(mClientSocket != null)
                    {
                        try
                        {
                            // prepare netEvent message
                            msg = new NetEvent();
                            msg.setEventID(NetEvent.NetEventType.TYPE_RECEIVE);
                            
                            //read the bytes
                            int read = mIStream.read(rawbuffer);
                            
                            if(read != -1)
                            {
//                                if(read > 3)
//                                {
                                    // get the opcode
                                    msg.setMsgID(rawbuffer[0]);

                                    // set the msg data
                                    msg.setData(rawbuffer);
                                    publishProgress(msg);
//                                }
                            }
                            else
                                throw new Exception();

                        }
                        catch(Exception e)
                        {
                            if(mIsSimulationStarted)
                            {
                                msg = new NetEvent();
                                msg.setEventID(NetEvent.NetEventType.TYPE_DISCON);
                                msg.setMsg("Client: " + mIP + " disconnected!");
                                publishProgress(msg);
                                
                                mClientSocket.close();
                                mClientSocket = null;
                            }
                        }
                    }
                }
            }
            catch(Exception e)
            {
                if(mIsSimulationStarted)
                {
                    msg = new NetEvent();
                    msg.setEventID(NetEvent.NetEventType.TYPE_NOTIFI);
                    msg.setMsg( "Error: " + e.getMessage() );
                    publishProgress(msg);
                }
            }
        }

        /**
         * Closing the thread
         */
        public synchronized void terminate()
        {
            try
            {
                // deinit
                if(mListeningSocket != null)
                {
                    mListeningSocket.close();
                    mListeningSocket = null;
                }

                // close the socket if it has been openened
                if(mClientSocket != null)
                {
                    mClientSocket.close();
                    mClientSocket = null;
                }
            }
            catch(IOException e)
            {
                Logger.AddLog(e.getClass() + ": " + e.getMessage());
            }
        }
        
        /**
         * publish the events to the GUI thread
         * @param event 
         */
        private void publishProgress(final NetEvent event)
        {
            java.awt.EventQueue.invokeLater(new  Runnable() {
                @Override
                public void run() 
                {
                    if(mNetListener != null)
                    {
                        mNetListener.onNetEvent(event);
                    }
                }
            });
        }
    }
        
            /**
     * Inner class
     * @author Mark van der Wal
     */
    public class SensorSender extends Thread
    {
        private long mCurrentTime;
        private long mLastTime;
        private long mDeltaTime;
        private long mInputTimer;
	private final int SENSOR_INTERVAL = 1000;
        private boolean mIsStarted;
        
        private Random generator;
                
        public SensorSender()
        {
            generator = new Random(System.currentTimeMillis());
        }
        
         /**
         * Start the server
         */
        @Override
        public void start()
        {
            super.start();
            mIsStarted = true;
        }

            /**
         * run the listening thread
         */
        @Override
        public void run() 
        {  
            mCurrentTime = System.currentTimeMillis();
            mLastTime = mCurrentTime;
            mInputTimer = 0;
            byte[] buffer;

            while( mIsStarted )
            {
                if(mOStream != null)
                {
                    // get the deltaTime
                    mCurrentTime = System.currentTimeMillis();
                    mDeltaTime = mCurrentTime - mLastTime;
                    mInputTimer += mDeltaTime;

                    if( mInputTimer > SENSOR_INTERVAL)
                    {                        
                        try
                        {
                            // test data
                            buffer = ProtocolEncoder.EncodeSensors( (byte)( (generator.nextInt() % 256 ) % 120),
                                    (byte)(generator.nextInt() % 256),
                                    (byte)(generator.nextInt() % 256),
                                    (byte)(generator.nextInt() % 256),
                                    (byte)(generator.nextInt() % 256),
                                    (byte)(generator.nextInt() % 256)
                            );
                                    
                            // Sensor data
                            mOStream.write(buffer);

                            // flush
                            mOStream.flush();
                        }
                        catch(Exception ex)
                        {
                            mIsStarted = false;
                        }
                        
                        // reset the timer
                        mInputTimer -= SENSOR_INTERVAL;
                    }

                    mLastTime = mCurrentTime;
                }
                else
                {
                    mIsStarted = false;
                }
            }
        }
        
                /**
         * Closing the thread
         */
        public synchronized void terminate()
        {
            mIsStarted = false;
        }
    }
}